use itertools::Itertools;

/// 上下左右四个方向
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Dir {
    Up,
    Right,
    Down,
    Left,
}

/// 一些信息
mod info {
    use super::Dir::{self, *};

    /// 顺序
    ///
    /// `(a, b)`代表`a`顺时针旋转一次后成为`b`。
    pub const ORDER: &[(Dir, Dir)] = &[(Up, Right), (Right, Down), (Down, Left), (Left, Up)];

    /// 符号
    pub const SYMBOL: &[(Dir, char)] = &[(Up, '^'), (Right, '>'), (Down, 'v'), (Left, '<')];
}

impl Dir {
    /// 顺时针旋转九十度
    pub fn rotate_clockwise(self) -> Self {
        info::ORDER.iter().find(|(dir, _)| self == *dir).unwrap().1
    }
    // 逆时针旋转九十度
    pub fn rotate_anticlockwise(self) -> Self {
        info::ORDER.iter().find(|(_, dir)| self == *dir).unwrap().0
    }

    /// 到符号去
    pub fn to_char(self) -> char {
        info::SYMBOL.iter().find(|(dir, _)| self == *dir).unwrap().1
    }
    /// 从符号来
    pub fn try_from_char(c: char) -> Option<Self> {
        info::SYMBOL
            .iter()
            .find(|(_, c2)| c == *c2)
            .map(|(dir, _)| *dir)
    }
}

impl std::fmt::Display for Dir {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use std::fmt::Write;
        f.write_char(self.to_char())
    }
}

impl std::str::FromStr for Dir {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let c = s.chars().exactly_one().map_err(|_| ())?;
        Dir::try_from_char(c).ok_or(())
    }
}

#[cfg(test)]
mod test {
    use super::{info, Dir};
    use proptest::{prop_assert_eq, proptest};

    #[test]
    fn rotate_unit() {
        for (a, b) in info::ORDER {
            assert_eq!(a.rotate_clockwise(), *b);
            assert_eq!(*a, b.rotate_anticlockwise());
        }
    }

    #[test]
    fn char_unit() {
        for (dir, c) in info::SYMBOL {
            assert_eq!(dir.to_char(), *c);
            assert_eq!(*dir, Dir::try_from_char(*c).unwrap());
        }
    }

    proptest!(
        #[test]
        fn fuzz(s in "[\\^v<>]") {
            let dir: Dir = s.parse().unwrap();
            prop_assert_eq!(dir.to_string(), s);
            prop_assert_eq!(
                dir.rotate_clockwise().rotate_clockwise(),
                dir.rotate_anticlockwise().rotate_anticlockwise()
            );
        }
    );
}
